/**
 * \file Pic.cc
 * \brief PCI facilities
 * \author Corey Tabaka
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <platform/Pci.h>
#include <platform/Descriptor.h>
#include <Sync.h>
#include <Log.h>

#include <i386/hw-instr.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct {
	uint16 size;
	void *offset;
	uint16 selector;
} __attribute__ ((packed)) irq_routing_options_t;

/*
 * Pointers to installed pci routines
 */
int (*g_pci_find_pci_device)(pci_location_t *state, uint16 device_id,
	uint16 vendor_id, uint16 index);
int (*g_pci_find_pci_class_code)(pci_location_t *state, uint32 class_code,
	uint16 index);
 
int (*g_pci_read_config_byte)(pci_location_t *state, uint32 reg, uint8 *value);
int (*g_pci_read_config_word)(pci_location_t *state, uint32 reg, uint16 *value);
int (*g_pci_read_config_dword)(pci_location_t *state, uint32 reg,
	uint32 *value);

int (*g_pci_write_config_byte)(pci_location_t *state, uint32 reg, uint8 value);
int (*g_pci_write_config_word)(pci_location_t *state, uint32 reg, uint16 value);
int (*g_pci_write_config_dword)(pci_location_t *state, uint32 reg,
	uint32 value);
	
int (*g_pci_get_irq_routing_options)(irq_routing_options_t *options,
	uint16 *pciIrqs);
int (*g_pci_set_irq_hw_int)(pci_location_t *state, uint8 int_pin, uint8 irq);

int pci_type1_detect(void);
static bool pciBiosDetect(void);

/*
 * Lock for preventing PCI BIOS reentrancy and for protecting device access
 * consistency.
 */
static InterruptLock pciLock;

void Pci::initialize(void) {
	if (pciBiosDetect()) {
		printf("        PCI BIOS functions installed\n");
		
		/*
		printf("Scanning...\n");
		
		pci_location_t state;
		
		for (state.bus = 0; state.bus < 0xff; state.bus++) {
			for (state.dev_fn = 0; state.dev_fn < 0xff; state.dev_fn++) {
				// do test/config
			}
		}
		
		printf("... done. Scanned %d busses, %d device/functions\n",
			state.bus + 1, (state.bus + 1) * (state.dev_fn + 1));
		*/
	}
}

int Pci::findDevice(pci_location_t &state, uint16 device_id,
	uint16 vendor_id, uint16 index) {
	pciLock.lock();
	
	int res = g_pci_find_pci_device(&state, device_id, vendor_id, index);
	
	pciLock.unlock();
	
	return res;
}

int Pci::findClassCode(pci_location_t &state, uint32 class_code,
	uint16 index) {
	pciLock.lock();
	
	int res = g_pci_find_pci_class_code(&state, class_code, index);
	
	pciLock.unlock();
	
	return res;
}

int Pci::readConfig(pci_location_t &state, uint32 reg, uint8 *value) {
	pciLock.lock();
	
	int res = g_pci_read_config_byte(&state, reg, value);
	
	pciLock.unlock();
	
	return res;
}

int Pci::readConfig16(pci_location_t &state, uint32 reg, uint16 *value) {
	pciLock.lock();
	
	int res = g_pci_read_config_word(&state, reg, value);
	
	pciLock.unlock();
	
	return res;
}

int Pci::readConfig32(pci_location_t &state, uint32 reg, uint32 *value) {
	pciLock.lock();
	
	int res = g_pci_read_config_dword(&state, reg, value);
	
	pciLock.unlock();
	
	return res;
}

int Pci::writeConfig(pci_location_t &state, uint32 reg, uint8 value) {
	pciLock.lock();
	
	int res = g_pci_write_config_byte(&state, reg, value);
	
	pciLock.unlock();
	
	return res;
}

int Pci::writeConfig16(pci_location_t &state, uint32 reg, uint16 value) {
	pciLock.lock();
	
	int res = g_pci_write_config_word(&state, reg, value);
	
	pciLock.unlock();
	
	return res;
}

int Pci::writeConfig32(pci_location_t &state, uint32 reg, uint32 value) {
	pciLock.lock();
	
	int res = g_pci_write_config_dword(&state, reg, value);
	
	pciLock.unlock();
	
	return res;
}

int Pci::getIrqRoutingOptions(irq_routing_entry *entries, uint16 &count,
	uint16 &pciIrqs) {
	pciLock.lock();
	
	irq_routing_options_t options;
	options.size = sizeof(irq_routing_entry) * count;
	options.selector = DATA_SELECTOR;
	options.offset = entries;
	
	int res = g_pci_get_irq_routing_options(&options, &pciIrqs);
	
	count = options.size / sizeof(irq_routing_entry);
	
	pciLock.unlock();
	
	return res;
}

int Pci::setIrqHwInt(pci_location_t &state, uint8 int_pin, uint8 irq) {
	pciLock.lock();
	
	int res = g_pci_set_irq_hw_int(&state, int_pin, irq);
	
	pciLock.unlock();
	
	return res;
}

#define PCIBIOS_PRESENT					0xB101
#define PCIBIOS_FIND_PCI_DEVICE			0xB102
#define PCIBIOS_FIND_PCI_CLASS_CODE		0xB103
#define PCIBIOS_GENERATE_SPECIAL_CYCLE	0xB106
#define PCIBIOS_READ_CONFIG_BYTE		0xB108
#define PCIBIOS_READ_CONFIG_WORD		0xB109
#define PCIBIOS_READ_CONFIG_DWORD		0xB10A
#define PCIBIOS_WRITE_CONFIG_BYTE		0xB10B
#define PCIBIOS_WRITE_CONFIG_WORD		0xB10C
#define PCIBIOS_WRITE_CONFIG_DWORD		0xB10D
#define PCIBIOS_GET_IRQ_ROUTING_OPTIONS	0xB10E
#define PCIBIOS_PCI_SET_IRQ_HW_INT		0xB10F

#define PCIBIOS_SUCCESSFUL				0x00
#define PCIBIOS_FUNC_NOT_SUPPORTED		0x81
#define PCIBIOS_BAD_VENDOR_ID			0x83
#define PCIBIOS_DEVICE_NOT_FOUND		0x86
#define PCIBIOS_BAD_REGISTER_NUMBER		0x87
#define PCIBIOS_SET_FAILED				0x88
#define PCIBIOS_BUFFER_TOO_SMALL		0x89

/*
 * Far call structure used by BIOS32 routines
 */
static struct {
	uint32 offset;
	uint16 selector;
} __attribute__ ((packed)) bios32_entry;

/*
 * BIOS32 entry header
 */
typedef struct {
	uint8 magic[4];		// "_32_"
	void * entry;		// entry point into
	uint8 revision;
	uint8 length;
	uint8 checksum;
	uint8 reserved[5];
} __attribute__ ((packed)) PCIBiosInfo;

// Scan for pci bios
const char * pciHead = "_32_";
static uint32 * checkPCI(void) {
	uint32 * head = (uint32 *) 0x000e0000;
	
	while (head < (uint32 *) 0x000ffff0) {
		if (*head == *((uint32 *) pciHead)) {
			// perform the checksum
			sint8 sum = 0;
			sint8 *b = (sint8 *) head;
			for (uint32 i=0; i < sizeof(PCIBiosInfo); i++) {
				sum += b[i];
			}
			
			if (sum == 0) {
				return head;
			}
		}
		
		head += 4;
	}
	
	return 0;
}

/*
 * Local BIOS32 PCI routines
 */
static int bios_find_pci_device(pci_location_t *state, uint16 device_id,
	uint16 vendor_id, uint16 index) {
	uint32 bx, ret;

	__asm__("lcall *(%%edi)\n\t"
		"jc 1f\n\t"
		"xor %%ah,%%ah\n"
		"1:"
		: "=b"(bx),
		  "=a"(ret)
		: "1"(PCIBIOS_FIND_PCI_DEVICE),
		  "c"(device_id),
		  "d"(vendor_id),
		  "S"(index),
		  "D"(&bios32_entry));
	
	state->bus = bx >> 8;
	state->dev_fn = bx & 0xFF;
	
	ret >>= 8;
	return ret & 0xFF;
}

static int bios_find_pci_class_code(pci_location_t *state, uint32 class_code,
	uint16 index) {
	uint32 bx, ret;

	__asm__("lcall *(%%edi)\n\t"
		"jc 1f\n\t"
		"xor %%ah,%%ah\n"
		"1:"
		: "=b"(bx),
		  "=a"(ret)
		: "1"(PCIBIOS_FIND_PCI_CLASS_CODE),
		  "c"(class_code),
		  "S"(index),
		  "D"(&bios32_entry));
	
	state->bus = bx >> 8;
	state->dev_fn = bx & 0xFF;
	
	ret >>= 8;
	return ret & 0xFF;
}


static int bios_read_config_byte(pci_location_t *state, uint32 reg,
	uint8 *value) {
	uint32 bx, ret;

	bx = state->bus;
	bx <<= 8;
	bx |= state->dev_fn;
	__asm__("lcall *(%%esi)\n\t"
		"jc 1f\n\t"
		"xor %%ah,%%ah\n"
		"1:"
		: "=c"(*value),
		  "=a"(ret)
		: "1"(PCIBIOS_READ_CONFIG_BYTE),
		  "b"(bx),
		  "D"(reg),
		  "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xFF;
}

static int bios_read_config_word(pci_location_t *state, uint32 reg,
	uint16 *value) {
	uint32 bx, ret;

	bx = state->bus;
	bx <<= 8;
	bx |= state->dev_fn;
	__asm__("lcall *(%%esi)\n\t"
		"jc 1f\n\t"
		"xor %%ah,%%ah\n"
		"1:"
		: "=c"(*value),
		  "=a"(ret)
		: "1"(PCIBIOS_READ_CONFIG_WORD),
		  "b"(bx),
		  "D"(reg),
		  "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xFF;
}

static int bios_read_config_dword(pci_location_t *state, uint32 reg,
	uint32 *value) {
	uint32 bx, ret;

	bx = state->bus;
	bx <<= 8;
	bx |= state->dev_fn;
	__asm__("lcall *(%%esi)\n\t"
		"jc 1f\n\t"
		"xor %%ah,%%ah\n"
		"1:"
		: "=c"(*value),
		  "=a"(ret)
		: "1"(PCIBIOS_READ_CONFIG_DWORD),
		  "b"(bx),
		  "D"(reg),
		  "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xFF;
}

static int bios_write_config_byte(pci_location_t *state, uint32 reg,
	uint8 value) {
	uint32 bx, ret;

	bx = state->bus;
	bx <<= 8;
	bx |= state->dev_fn;
	 __asm__("lcall *(%%esi)\n\t"
		 "jc 1f\n\t"
		 "xor %%ah,%%ah\n"
		 "1:"
		 : "=a"(ret)
		 : "0"(PCIBIOS_WRITE_CONFIG_BYTE),
		   "c"(value),
		   "b"(bx),
		   "D"(reg),
		   "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xFF;
}

static int bios_write_config_word(pci_location_t *state, uint32 reg,
	uint16 value) {
	uint32 bx, ret;

	bx = state->bus;
	bx <<= 8;
	bx |= state->dev_fn;
	 __asm__("lcall *(%%esi)\n\t"
		 "jc 1f\n\t"
		 "xor %%ah,%%ah\n"
		 "1:"
		 : "=a"(ret)
		 : "0"(PCIBIOS_WRITE_CONFIG_WORD),
		   "c"(value),
		   "b"(bx),
		   "D"(reg),
		   "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xFF;
}

static int bios_write_config_dword(pci_location_t *state, uint32 reg,
	uint32 value) {
	uint32 bx, ret;

	bx = state->bus;
	bx <<= 8;
	bx |= state->dev_fn;
	 __asm__("lcall *(%%esi)\n\t"
		 "jc 1f\n\t"
		 "xor %%ah,%%ah\n"
		 "1:"
		 : "=a"(ret)
		 : "0"(PCIBIOS_WRITE_CONFIG_DWORD),
		   "c"(value),
		   "b"(bx),
		   "D"(reg),
		   "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xFF;
}

static int bios_get_irq_routing_options(irq_routing_options_t *routeBuffer,
	uint16 *pciIrqs) {
	uint32 ret;
	
	__asm__("lcall *(%%esi)\n\t"
		"jc 1f\n\t"
		"xor %%ah,%%ah\n"
		"1:"
		: "=b"(*pciIrqs),
		  "=a"(ret)
		: "1"(PCIBIOS_GET_IRQ_ROUTING_OPTIONS),
		  "b"(0),
		  "D"(routeBuffer),
		  "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xff;
}

static int bios_set_irq_hw_int(pci_location_t *state, uint8 int_pin,
	uint8 irq) {
	uint32 bx, cx, ret;

	bx = state->bus;
	bx <<= 8;
	bx |= state->dev_fn;
	cx = irq;
	cx <<= 8;
	cx |= int_pin;
	__asm__("lcall *(%%esi)\n\t"
		"jc 1f\n\t"
		"xor %%ah,%%ah\n"
		"1:"
		: "=a"(ret)
		: "0"(PCIBIOS_PCI_SET_IRQ_HW_INT),
		  "b"(bx),
		  "c"(cx),
		  "S"(&bios32_entry));
	ret >>= 8;
	return ret & 0xFF;
}

const char *pciSignature = "PCI ";
static bool pciBiosDetect(void) {
	PCIBiosInfo * pci;
	
	if ((pci = (PCIBiosInfo *) checkPCI()) > 0) {
		/*printf("Found PCI structure at %08x\n", (uint32) pci);
			
		printf("\nPCI header info:\n");
		printf("%c%c%c%c\n", pci->magic[0], pci->magic[1], pci->magic[2],
			pci->magic[3]);
		printf("%08x\n", (uint32) pci->entry);
		printf("%d\n", pci->length * 16);
		printf("%d\n", pci->checksum);*/
		
		uint32 adr, temp, len;
		uint8 err;
		
		bios32_entry.offset = (uint32) pci->entry;
		bios32_entry.selector = CODE_SELECTOR;
		
		__asm__("lcall *(%%edi)"
			: "=a"(err),	/* AL out=status */
			  "=b"(adr),	/* EBX out=code segment base adr */
			  "=c"(len),	/* ECX out=code segment size */
			  "=d"(temp)	/* EDX out=entry pt offset in code */
			: "0"(0x49435024),/* EAX in=service="$PCI" */
			  "1"(0),	/* EBX in=0=get service entry pt */
			  "D"(&bios32_entry));
		
		if (err == 0x80) {
			printf("BIOS32 found, but no PCI BIOS\n");
			return false;
		}
		
		if (err != 0) {
			printf("BIOS32 call to locate PCI BIOS returned %x\n", err);
			return false;
		}
		
		// got it
		
		bios32_entry.offset = adr + temp;
		
		// now call PCI_BIOS_PRESENT to get version, hw mechanism, and last bus
		uint16 present, version, busses;
		uint32 signature;
		__asm__("lcall *(%%edi)\n\t"
			"jc 1f\n\t"
			"xor %%ah,%%ah\n"
			"1:"
			: "=a"(present),
			  "=b"(version),
			  "=c"(busses),
			  "=d"(signature)
			: "0"(PCIBIOS_PRESENT),
			  "S"(&bios32_entry));
		
		if (present & 0xff00) {
			printf("PCI_BIOS_PRESENT call returned ah=%02x\n", present >> 8);
			return false;
		}
		
		if (signature != *(uint32 *)pciSignature) {
			printf("PCI_BIOS_PRESENT call returned edx=%08x\n", signature);
			return false;
		}
		
		g_pci_find_pci_device = bios_find_pci_device;
		g_pci_find_pci_class_code = bios_find_pci_class_code;
		
		g_pci_read_config_dword = bios_read_config_dword;
		g_pci_read_config_word = bios_read_config_word;
		g_pci_read_config_byte = bios_read_config_byte;
	
		g_pci_write_config_dword = bios_write_config_dword;
		g_pci_write_config_word = bios_write_config_word;
		g_pci_write_config_byte = bios_write_config_byte;
		
		g_pci_get_irq_routing_options = bios_get_irq_routing_options;
		g_pci_set_irq_hw_int = bios_set_irq_hw_int;
		
		return true;
	}
	
	return false;
}
